﻿using System;
using System.Net.Sockets;
using System.Net;
using System.Collections.Generic;
using System.Text;

namespace TcpServer
{
    class Program
    {
        const int C = 60;

        static List<Client> clients = new List<Client>();
        static TcpListener Listener;

        static void Main(string[] args)
        {
            ConsoleKeyInfo cki;
            WriteMenu();

            while (true)
            {
                cki = Console.ReadKey(true);

                switch (cki.Key)
                {
                    case ConsoleKey.Escape:
                        StopServer();
                        Environment.Exit(0);
                        break;
                    case ConsoleKey.D1:
                        WriteMenu();
                        break;
                    case ConsoleKey.D2:
                        StartServer();
                        break;
                    case ConsoleKey.D3:
                        StopServer();
                        break;
                    case ConsoleKey.D4:
                        PrintClients();
                        break;
                    case ConsoleKey.D5:
                        WriteServer();
                        break;
                }
            }
        }

        static void WriteMenu()
        {
            Console.WriteLine(new String('-', C));
            Console.WriteLine("MENU:");
            Console.WriteLine("Esc - Exit.");
            Console.WriteLine("1 - Print menu.");
            Console.WriteLine("2 - Start server.");
            Console.WriteLine("3 - Stop server.");
            Console.WriteLine("4 - Print clients.");
            Console.WriteLine("5 - Send for all clients.");
        }

        static void StartServer()
        {
            IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
            IPAddress ipAddress = ipHostInfo.AddressList[0];
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

            Listener = new TcpListener(localEndPoint);
            Listener.Start();
            Console.WriteLine(new String('-', C));
            Console.WriteLine("Server start. Listening...");
            Listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), Listener); 
        }

        static void StopServer()
        {
            if (Listener == null) return;

            Listener.Stop();
            lock (clients)
            {
                foreach (Client client in clients)
                {
                    client.TcpClient.Client.Disconnect(false);
                }
                clients.Clear();
            }
            Listener = null;
        }

        static void WriteServer()
        {
            if (Listener == null)
            {
                Console.WriteLine(new String('-', C));
                Console.WriteLine("First start server (Press 2).");
                return;
            }

            NetworkStream stream;
            byte[] bytes = Encoding.Default.GetBytes(new String('1', 50));

            foreach (Client client in clients)
            {
                stream = client.TcpClient.GetStream();
                stream.BeginWrite(bytes, 0, bytes.Length, WriteCallback, client);
            }
        }

        static void PrintClients()
        {
            int i = 0;
            Console.WriteLine(new String('-', C));
            Console.WriteLine("Clients:");
            lock (clients)
            {
                foreach (Client client in clients)
                {
                    i++;
                    IPAddress ip = ((IPEndPoint)client.TcpClient.Client.RemoteEndPoint).Address;
                    Console.WriteLine("{0}: {1}", i, ip);
                }
            }
        }

        static void AcceptCallback(IAsyncResult iar)
        {
            TcpListener l = (TcpListener)iar.AsyncState;
            TcpClient c;
            try
            {
                c = l.EndAcceptTcpClient(iar);

                Console.WriteLine(new String('-', C));
                Console.WriteLine(
                    "Connected: client {0}.",
                    ((IPEndPoint)c.Client.RemoteEndPoint).Address);

                byte[] buffer = new byte[c.ReceiveBufferSize];
                Client client = new Client(c, buffer);
                lock (clients)
                {
                    clients.Add(client);
                }
                c.GetStream().BeginRead(
                    client.Buffer, 0, client.Buffer.Length, ReadCallback, client);
                // keep listening
                Console.WriteLine(new String('-', C));
                Console.WriteLine("Listening...");
                l.BeginAcceptTcpClient(AcceptCallback, l);
            }
            catch (SocketException ex)
            {
                Console.WriteLine("Error accepting TCP connection: {0}", ex.Message);
                return;
            }
            catch (ObjectDisposedException)
            {
                Console.WriteLine(new String('-', C));
                Console.WriteLine("Listen canceled. Server stopped.");
                return;
            }
        }

        static void ReadCallback(IAsyncResult result)
        {
            Client client = result.AsyncState as Client;
            if (client == null) return;

            if (!client.TcpClient.Connected)
            {
                Console.WriteLine(new String('-', C));
                Console.WriteLine(
                    "Server disconnect client {0}.",
                    ((IPEndPoint)client.TcpClient.Client.RemoteEndPoint).Address);

                return;
            }
            NetworkStream networkStream = client.TcpClient.GetStream();
            int read = networkStream.EndRead(result);
            if (read == 0)
            {
                Console.WriteLine(new String('-', C));
                Console.WriteLine(
                    "Disconnected: client {0}.",
                    ((IPEndPoint)client.TcpClient.Client.RemoteEndPoint).Address);

                lock (clients)
                {
                    clients.Remove(client);
                    return;
                }
            }

            Console.WriteLine(new String('-', C));
            Console.WriteLine(
                "Received: client {0}, length {1}",
                ((IPEndPoint)client.TcpClient.Client.RemoteEndPoint).Address,
                read);

            string data = Encoding.Default.GetString(client.Buffer, 0, read);
            //Do something with the data object here.
            networkStream.BeginRead(
                client.Buffer, 0, client.Buffer.Length, ReadCallback, client);
        }

        static void WriteCallback(IAsyncResult result)
        {
            Client client = result.AsyncState as Client;

            Console.WriteLine(new String('-', C));
            Console.WriteLine(
                "Sended: client {0}",
                ((IPEndPoint)client.TcpClient.Client.RemoteEndPoint).Address);

            NetworkStream stream = client.TcpClient.GetStream();
            stream.EndWrite(result);
        }


    }

    internal class Client
    {
        public Client(TcpClient tcpClient, byte[] buffer)
        {
            if (tcpClient == null) throw new ArgumentNullException("tcpClient");
            if (buffer == null) throw new ArgumentNullException("buffer");
            this.TcpClient = tcpClient;
            this.Buffer = buffer;
        }
        public TcpClient TcpClient { get; private set; }
        public byte[] Buffer { get; private set; }
    }

}
